home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Games 1996 July
/
Amiga Games 1996 #7.iso
/
spiele
/
publicdomain
/
tads
/
tads.doc
< prev
next >
Wrap
Text File
|
1996-02-20
|
60KB
|
1,511 lines
TADS: The Text Adventure Development System
Software for implementing text adventure games
by Michael J. Roberts
Overview Documentation
Copyright (c) 1992 by Michael J. Roberts.
All Rights Reserved.
------------------------------------------------------------------------------
Introduction
This is a brief overview of TADS, a program that makes it easier to
write your own text adventure games. TADS is offered as shareware,
which means that you can try the software for free, but you must
pay for the software if you decide you like it and want to continue
using it. Registering your copy of TADS gives you many benefits:
- You get the full TADS Author's Manual -- over 200 pages of
detailed information about how to use TADS, including many
programming examples. The TADS Author's Manual describes
all of the features of TADS in detail.
- You get TDB, the TADS Debugger -- a full-featured source-level
debugger that lets you examine the internal workings of your
game while it runs. With TDB, you can single-step through
your program's source code, set breakpoints at specific lines
of code, and examine and change the values of variables in
your program.
- You'll be notified of future upgrades to TADS, and of other
products that we develop.
- Your support helps ensure that we can continue to enhance
and support TADS.
Of course, we want you to be able to determine if TADS is useful
to you, so we've provided this overview of the system. This
overview is intended to help you get started with TADS; the
TADS Author's Manual has full details of the topics described
in this summary.
------------------------------------------------------------------------------
Ditch Day Drifter
We've included a large sample game, Ditch Day Drifter, with TADS.
The source for the game is in the file DITCH.T. We included Ditch Day
Drifter as a sample of what TADS can do, and to help you see how you
can go about writing your own game with TADS. After reading through
this description of TADS, you may find it helpful to look at DITCH.T
to see more examples of how to write TADS code.
If you want to play Ditch Day Drifter, you need to compile it first.
On MS-DOS machines, go to the TADS directory and type this:
tc ditch
The "tc" command runs the TADS Compiler, which converts the game
source file into a binary executable format. Run the game with
this command:
tr ditch
On Macintosh systems, start the compiler by double-clicking on the
application named "TADS Compiler". The compiler will display a dialog
box with several parameters. Click on the "Select" button next to the
"Filename" box, then select the file DITCH.T using the normal file
selector dialog that appears. Then, click on the "Compile" button.
When the compiler finishes, click on the "Quit" button. Now you
can run the game either by double-clicking on the new document
DITCH.GAM, or by double-clicking on the application "TADS Run-Time",
then selecting DITCH.GAM from the file selector dialog.
------------------------------------------------------------------------------
What is TADS?
TADS is a tool that makes it easier to write text adventure games.
The system consists of a compiler, which reads source code written
in the TADS language, checks it for errors, and converts it to an
internal representation; and a run-time system, which reads commands
typed by the player, and controls the interaction between the player
and your game program.
It's easier to write an adventure using TADS than with a general
purpose language for several reasons.
- The TADS language is specially designed for text adventure
games, so the things you do most frequently in programming
an adventure are easy to do.
- The run-time system provides an excellent player command
parser -- you don't need to worry about parsing player
commands at all.
- All of the work of saving and restoring games, undoing
commands, controlling the on-screen display, and many
other low-level tasks, is done automatically by the system.
- TADS includes a large set of common adventure objects,
implemented using the TADS language. You can use these
objects as they are, or make your own customizations.
- Games written with TADS are instantly portable to every
system on which the TADS run-time system is available --
you don't even need to recompile your game!
------------------------------------------------------------------------------
Writing games with TADS
To write a game with TADS, you start off by writing your game's
source file with a text editor; you can use any editor that can
produce a plain text file. Once you've written your source file,
you compile it using the TADS compiler. If the game had no errors,
you can run the game using the TADS run-time system. As you write
your game, you'll probably write a little, compile it, try it out,
then go back and add more, compile and try that, and so on.
------------------------------------------------------------------------------
Getting Started - A Sample Game
To help you get started writing your own games with TADS, we'll go
through the implementation of a sample game. This example is similar
to the one used in chapter one of the TADS Author's Manual.
We'll start with about the simplest game possible: two rooms, and
no items. We could start with only one room, but then there wouldn't
be anything to do while playing the game; with two rooms, at least
the player can move between the rooms.
If you want to try running this game, create a file containing the
program text shown below using your text editor or word processor.
The TADS compiler will accept an ASCII file saved by any text editor.
If you're using a word processor, you may have to choose a special
option to save the file as plain text, without any formatting codes;
refer to your word processor's documentation for details.
Note that the lines of equal signs are just borders to show where the
example starts and ends; use only the part between the lines of equal
signs when creating the file.
==============================================================================
/* This is a comment, just like in C */
#include <adv.t> /* read basic adventure game definitions file */
#include <std.t> /* read starting standard functions file */
startroom: room /* the game always starts in startroom */
sdesc = "Outside cave" /* the Short DESCription of the room */
ldesc = "You're standing in the bright sunlight just
outside of a large, dark, forboding cave, which
lies to the north."
north = cave /* the room called "cave" lies to the north */
;
cave: room
sdesc = "Cave"
ldesc = "You're inside a dark and musty cave. Sunlight
pours in from a passage to the south."
south = startroom
;
==============================================================================
To run this example, all you have to do is compile it with TC, the TADS
Compiler, then run it with TR, the TADS Run-time system. If you named
your sample program file "mygame.t", on most operating systems you can
compile it with this command:
tc mygame
and you can run it with this command:
tr mygame
Let's look at the sample program line by line.
The first line is a #include command. This command inserts another
source file into your program. The file "adv.t" (included with the
TADS package) is a set of basic definitions that can be used by most
adventure games. By including adv.t in your game, you don't need to
worry about defining words such as "the", a large set of verbs (such
as "take", "north", and other common verbs), and many "object classes"
(described later).
The next line includes the file "std.t" (which is also included with
the TADS package), which contains additional definitions. The reason
for placing some definitions in the separate file std.t is that you
will almost always want to change most of the definitions in std.t in
a finished game, whereas the definitions in adv.t can be used unchanged
by many games. (Even though most finished games will customize the
definitions in std.t, the definitions are good enough to get us started
with this sample game.)
The next line says "startroom: room". This tells the compiler that
you're defining a room named "startroom". A room is nothing special
to TADS, but adv.t, which we previously included, defines what a room
is. A room is one of the object classes that we mentioned earlier.
The next line defines the "sdesc" for this room. The sdesc is a
short description; for a room, it is displayed whenever a player
enters the room. The next line is the "ldesc", or long description;
it is displayed when the player enters the room for the first time,
or asks for the full description with the "look" command. Finally,
the "north" definition says that another room, called "cave", is
reached when the player types "north" while in startroom.
Now let's add a few items that can be manipulated by the player.
We'll add a solid gold skull, and a pedestal for it to sit on.
Add these definitions at the end of the source you've already
created.
==============================================================================
pedestal: surface, fixeditem
sdesc = "pedestal"
noun = 'pedestal'
location = cave
;
goldSkull: item
sdesc = "gold skull"
noun = 'skull' 'head'
adjective = 'gold'
location = pedestal
;
==============================================================================
As with the "room" class, the "surface", "fixeditem", and "item" classes
are not built in to TADS, but are defined in adv.t. Note that you can
create an object using more than one class; in the example, the pedestal
object is both a surface and a fixeditem. A surface is an object that
can have other objects placed upon it; a fixeditem is an object that can't
be picked up and carried by the player. The goldSkull object is simply
an item, which is an object that the player can pick up and carry around.
As with a room, the sdesc property gives a short description of the object;
it should simply be the name of the object. There is no ldesc property
defined for these objects, so they get the default long descriptions
defined by their classes. The ldesc for an item simply says, for
example, "It looks like an ordinary gold skull"; for a surface, it
lists the objects sitting on the surface.
Since these objects can be manipulated by the player, they must be
associated with vocabulary words. The noun and adjective definitions
specify the words the player can use to refer to the objects. Note that
the sdesc and ldesc properties are enclosed in double quotes, but the
vocabulary words are enclosed in single quotes. Note also that a noun
or adjective can have multiple vocabulary words.
The objects have another new property as well: location. This simply
defines the object that contains the object being defined. In the case
of the pedestal, its location is the room "cave"; since the goldSkull
is on the pedestal, its location is the object "pedestal".
Now let's add another feature to the game: let's add a trap, so that
the player can't take the gold skull without getting killed. To do
this, we'll replace the goldSkull definition shown above with the new
definition below.
==============================================================================
goldSkull: item
sdesc = "gold skull"
noun = 'skull' 'head'
adjective = 'gold'
location = pedestal
doTake(actor) =
{
"As you lift the skull, a volley of poisonous
arrows is shot from the walls! You try to
dodge the arrows, but they take you by surprise!";
die();
}
;
==============================================================================
The definition of doTake (which stands for Direct Object Take) has an
argument specifying the character who is trying to take the object
(since we have no characters besides the player, the actor will be
the player's character, named "Me"). The system calls doTake whenever
the player attempts to take the object. Note that this definition of
doTake is associated with the object itself; another object could have
a different doTake that does something entirely different. In this
case, we simply display a message (since the message is enclosed in
double quotes, it is simply displayed when this statement is executed),
then call the "die" function, which is defined in std.t.
You might wonder why we've waited until now to define doTake in the
goldSkull object, or you might have just assumed that the system
automatically knows what to do if doTake is not defined for an object.
In fact, all objects do need a doTake definition, and the system has
no default behavior if the definition is missing. However, since
most objects will have a very similar doTake definition, it would be
extremely tedious to have to type the whole definition for every
normal object. Instead, we use something called "inheritance": by
defining the goldSkull to be a member of the "item" class, you tell
TADS that the goldSkull object inherits all of the definitions from
the item class, in addition to any definitions it makes on its own.
The item class (in adv.t) has a definition for doTake that is suitable
for most objects. However, if an object of class "item" has its
own definition of doTake, the one in the object takes precedence --
it "overrides" the default definition from the class.
We don't have a very good puzzle at this point, because there's no
way the player can get the gold skull without getting killed. So,
let's make it possible for the player to get the skull. We'll assume
that the mechanism that the pedestal uses to set off the poisoned
arrow trap is a weight sensor: when the pedestal has too little
weight on it, the trap is set off. So, we'll create a rock that
the player can use to keep the trap from going off: the player puts
the rock on the pedestal, then can take the skull. Add the new
definition below to the end of your source file.
==============================================================================
smallRock: item
sdesc = "small rock"
noun = 'rock'
adjective = 'small'
location = cave
;
==============================================================================
Now, we'll change the definition of doTake in the goldSkull object.
Replace the old definition of doTake with the one below.
==============================================================================
doTake(actor) =
{
if (self.location <> pedestal or smallRock.location = pedestal)
{
pass doTake;
}
else
{
"As you lift the skull, a volley of poisonous
arrows is shot from the walls! You try to dodge
the arrows, but they take you by surprise!";
die();
}
}
==============================================================================
This new doTake definition first checks to see if the object being taken
(the special object "self", which is the object whose doTake definition
is being executed), in this case, the gold skull, is already off the
pedestal; if it is, we don't want anything to happen, so we "pass"
doTake. We also pass doTake if the small rock is on the pedestal. When
we pass doTake, the doTake definition that we inherited from our parent
class (in this case, item) is invoked. This allows us to override a
definition only under certain special circumstances, but otherwise
use the default behavior. If the rock isn't on the pedestal, and the
skull is being removed from the pedestal, the poisoned arrows are
released as before.
We've included a file named GOLDSKUL.T that includes this sample
game, with the complete puzzle involving the gold skull and the
pedestal. You can compile and run that file by typing these
commands:
tc goldskul
tr goldskul
------------------------------------------------------------------------------
The TADS Language
If you know how to program in a "procedural" language like C or
Pascal, most of the TADS language will probably look very familiar.
However, the overall structure of a TADS program will probably seem
a little strange at first.
In particular, you'll probably wonder where the program begins.
The answer is that a TADS game doesn't have a beginning in the same
sense that a C or Pascal program does.
A C program, like a program written in any procedural language,
is a sequence of instructions. The program starts running at
the main() function, and carries out the instructions listed
there in the order they appear. The program may call subroutines
or iterate through loops along the way, but control flows
essentially sequentially through the program.
A TADS program, in contrast, is simply a series of object definitions.
Each definition specifies the behavior of a single object in the game.
An object definition consists of a series of "properties", which are
attributes of the object. A property can contain some simple
information, such as the name of the object, or its weight, or can
contain programming language code that defines the object's behavior.
Obviously, the information you specify in your object definitions
has to be used somehow -- something has to call the program code that
you write. In TADS, it's the player command parser that does the work
of deciding which properties to use in which objects in response to
the player's commands.
Later on, we'll describe in detail how the parser decides which properties
to evaluate. In order to take full advantage of the power of TADS by
creating your own types of objects, you'll need to understand how the
parser works. However, one of the advantages of using TADS is that
you can define objects using existing types without having to know
everything about how those types work. So, we'll start off by showing
you how to create some simple objects that you'll find in almost every
adventure game.
------------------------------------------------------------------------------
Creating a Room
The first type of object you'll want to create in an adventure game
is a room, which is simply a location in the game. The attributes of
a room are typically its name, its description, and links to other
rooms.
The first thing you'll need to do in defining a room, or any TADS
object, is to choose its object name. This is not a name that is
ever displayed to or used by the player -- it is a purely internal
name that you use to refer to the object within the TADS program.
It is similar to the name of a variable in C or Pascal. In TADS,
the name of an object must start with a letter, and can consist of
letters, numbers, and underscores.
Here's a sample room definition.
nsCrawl: room
sdesc = "North-South Crawl"
ldesc = "You're in a narrow, low passage connecting
caves to the north and south. A cold, damp
breeze comes from the north."
north = iceRoom
south = roundRoom
;
The first line of the object definition gives the object its name,
"nsCrawl", and specifies the type of the object. Note that the case
of the letters in the name is significant -- "nsCrawl" is a different
name than "NSCrawl" or "nscrawl".
The next line specifies a property of the object -- the sdesc, or
short description. It says that the property sdesc is the string
"North-South Crawl". The sdesc is displayed whenever the player
enters the room, and is also used on the status line.
In TADS, double-quoted strings are special: they mean that the
string is to be displayed whenever the property is evaluated.
Double-quoted strings are similar to using a PRINT statement in
other languages. So, whenever the sdesc property of the nsCrawl
object is evaluated, the string "North-South Crawl" will be displayed
to the player.
The next line defines the ldesc, or long description, property.
This is the full description of the room that is displayed when
the player enters the room for the first time, or whenever the player
uses the "look" command to ask for a full description. Note that
this property's value is a double-quoted string, just like the sdesc,
to indicate that the string is displayed whenever the ldesc is
evaluated.
The next two lines specify the directional properties north and south.
These properties are set to refer to other room objects. They specify
that the player will end up in the room defined by the object
iceRoom by going north, and in roundRoom by going south. Note that
other directions are not specified -- this simply means that the player
can't go in any other directions. The other possible direction properties
are east, west, up, down, in, out, ne, se, nw, and sw (the last four
stand for northeast, southeast, northwest, and southwest, respectively).
The final line of the room definition is a semicolon, which tells TADS
that the object definition is finished.
------------------------------------------------------------------------------
Creating Items
In addition to rooms, you'll want to create objects that the player
can pick up and carry around. These are objects of type "item".
When you define an item, you'll need to specify another set of
properties: its short and long descriptions (just like rooms),
its location (which says where the object is originally situated),
and vocabulary words that specify how the player can refer to the
object. Here are a couple of item definitions.
dollar: item
location = nsCrawl
sdesc = "one-dollar bill"
noun = 'dollar' 'bill'
adjective = 'one' 'one-dollar' '1' 'dollar'
;
rope: item
noun = 'rope'
sdesc = "rope"
ldesc = "It's about 50 feet long, and seems quite strong; it's
probably capable of handling several hundred pounds."
location = backpack
;
The first thing to notice here is that vocabulary words are enclosed
in single quotes, not double quotes. Recall that double-quoted strings
are displayed whenever evaluated; this is obviously not desirable for
vocabulary words. So, always specify vocabulary words with single quotes.
You may also notice that more than one single-quoted string can be
used with a vocabulary property. This is a unique feature of vocabulary
properties (the vocabulary properties are noun, adjective, plural,
verb, preposition, and article). When you use more than one word
in a vocabulary property, it simply creates multiple synonyms that can
be used to refer to the object.
Note that the properties can be defined in any order. Note also that
you don't need to specify every property; for example, if the ldesc
property is not specified with an item, a default message ("It looks
like an ordinary one-dollar bill") is displayed. However, practically
every item will define the properties sdesc, location, and noun.
The location property specifies the original location of the object.
In many cases, the location will refer to a room object. However, it
can also refer to another game item; in these cases, the item should
be a container, as described below.
------------------------------------------------------------------------------
Containers
You will often want to create items that can contain other items.
To do this, simply create an item of type "container" rather than
"item". In other respects, a "container" object is the same as
an "item", and you specify the same set of properties. Here's an
example.
backpack: container
sdesc = "backpack"
location = maintenanceRoom
noun = 'backpack' 'pack' 'bag'
adjective = 'back'
;
In addition to container objects, you can create "openable" objects.
These are the same as containers, except that they can be opened and
closed. When you create a container, there's a new property,
"isopen", that specifies whether the object is open or closed.
If you want the object to be closed initially, set isopen = nil.
Here's an example.
toolbox: openable
sdesc = "tool box"
location = maintenanceRoom
noun = 'toolbox' 'box'
adjective = 'tool'
isopen = nil
;
------------------------------------------------------------------------------
Fixed Items
You will frequently want to create items in the game that the player
can't pick up and carry around, but that are permanent features of
a room. For example, if you create a bedroom, you'll probably want
to have a bed and maybe a dresser in the room. It doesn't make sense
for the player to be able to carry off the furniture.
To create an item that the player can't carry, create an object of
type "fixeditem". You define fixeditem objects that same way that
you define item objects; the only difference is that fixeditem objects
can't be carried off by the player.
Here's an example.
bed: fixeditem
location = bedroom
noun = 'bed'
sdesc = "Bed"
;
------------------------------------------------------------------------------
Inheritance and ADV.T
Up to now, we've been describing the various types of objects that
you can create, and the properties that you have to define when
creating those objects. It may appear that these object types are
somehow built in to TADS. In fact, none of these types are special;
they're all defined using the TADS language.
The objects we've seen so far -- room, item, container, openable,
fixeditem -- are defined in the file ADV.T. This file is a set of
object type definitions that are suitable for most games, but they're
not at all special, so you can take them or leave them as you choose.
You could even completely rewrite ADV.T if you want your game to
behave differently.
Note that you can create objects using the types defined in ADV.T
(or in your own program) very easily -- as though the types were
somehow built in to TADS. The trick that makes this possible is
called "inheritance". The TADS language allows you to define an
object so that it inherits all of the properties of another object.
This is what you're doing when you create an object of type room:
you're telling TADS that it should create a new object exactly like
the object "room" that it's already seen, except that you want to
add some new properties of your own. If you look at ADV.T, you'll
see that the definition of the room object is very complex; however,
you can create objects of type room without having to know very
much about the room object itself -- all you need to know is what
properties you need to add to the new object.
ADV.T defines many more objects than we've talked about so far.
You should refer to ADV.T for complete information on the
standard adventure objects, and how to use them. Here's a brief
list.
nestedroom -- a room within another room
chairitem -- a chair (something the player can sit on)
beditem -- a bed (something the player can lie down on)
thing -- low-level item class (not normally used directly)
item -- a simple item the player can pick up and carry
lightsource -- something that provides light
hiddenItem -- an object that starts off hidden within another object
hider -- low-level hider class (not used directly)
underHider -- an object that can have objects hidden under it
behindHider -- an object that can have objects hidden behind it
searchHider -- an object that can have objects hidden within it
fixeditem -- an immovable item
readable -- something the player can read
fooditem -- something the player can eat
dialItem -- something the player can turn to various values
switchItem -- something the player can turn on and off
room -- a location in the game
darkroom -- a location without any lighting of its own
Actor -- a character in the game
moveableActor -- a character that the player can pick up and carry
follower -- a psuedo-object that can be used to follow actors
basicMe -- a set of definitions suitable for most player actors
decoration -- an item that serves no purpose other than decoration
buttonItem -- something the player can push
clothingItem -- something the player can wear
obstacle -- something that blocks travel
doorway -- one side of a door
lockableDoorway -- a door that can be locked
vehicle -- an item that can be used for travel
surface -- an item that can have objects placed upon it
container -- an item that can have objects placed within it
openable -- a container that can be opened and closed
qcontainer -- a container that doesn't list its contents automatically
lockable -- an openable that can be locked and unlocked
keyedLockable -- a lockable that needs a key to be locked and unlocked
keyItem -- an item that can lock and unlock a keyedLockable
transparentItem -- an object whose contents are visible
basicNumObj -- definition suitable for numObj in most games
basicStrObj -- definition suitable for strObj in most games
deepverb -- a verb object
travelVerb -- a verb object for travel verbs
sysverb -- a "system" verb
Prep -- a preposition object
Each object in ADV.T is fully described in a comment preceding its
definition. The comments tell what properties you need to define
when creating an object of each type, and how the object will behave.
------------------------------------------------------------------------------
Methods
We mentioned earlier that a property can contain a simple value,
such as a string or a number, or programming code that defines its
behavior. When a property contains code, it is called a "method".
With the exception of vocabulary properties, any property can
contain programming code instead of a simple value.
Programming code starts with a left brace ("{"), and ends with
a matching right brace ("}"). Within the braces, you can use
programming language statements that create local variables,
call functions and methods, assign values to properties and
variables, and control flow with loops and conditionals. The
programming code used in TADS methods looks similar to the C
language, but there are some differences.
Here's an example of an object incorporating a method. In this
case, the object defines its ldesc property using a method,
rather than a simple double-quoted string value, so that the
property can be sensitive to the state of a window.
attic: room
sdesc = "Attic"
ldesc =
{
"You're in a large, dusty attic. Old cobwebs hang
from the rafters. At the north end of the room is
a window, which is ";
if (atticWindow.isopen)
"open";
else
"closed";
". A ladder leads down.";
}
down = hallway
;
The ldesc property displays a message that depends on whether the
window is open or closed. For example, if the window is open, the
room's long description would look like this:
You're in a large, dusty attic. Old cobwebs hang from the
rafters. At the north end of the room is a window, which
is open. A ladder leads down.
------------------------------------------------------------------------------
Expressions
TADS expressions are entered in algebraic notation. For example,
to add two numbers, you would code an expression like this:
3 + 4
You can use parentheses to change the order of evaluation of the
parts of an expression. For example, since multiplication has higher
precedence than addition, the expression 3 + 4 * 5 evaluates to 23;
however, if you write this as (3 + 4) * 5, its value is 60, because
the addition is performed before the multiplication.
If you want to assign a value, you use the operator ":=" (a colon
followed immediately by an equals sign, without any intervening spaces).
For example, to assign the value nil to the isopen property of the
object atticWindow, you'd code this:
atticWindow.isopen := nil;
The period (or "dot") operator, ".", is used to take a property of
an object. In the example above, it specifies the isopen property of
the object atticWindow. You can use the value of a property in an
expression, or assign a new value to a property, using this operator.
Here are the TADS operators, shown in the order of evaluation.
& Takes the "address" of a function or property.
. Takes a property of an object: obj.prop evaluates property
prop of object obj.
[] List indexing. If var contains the list [5 4 3 2 1], then
var[2] is 4, var[4] is 2, var[5] is 1, and so on.
++ Increment a variable that contains a number, assigning the
incremented value back to the variable. If var contains
5, it will contain 6 after var++ is evaluated.
-- Decrement.
not Logical negation. not true is nil, and not nil is true.
- Arithmetic negation (when used as a unary prefix operator).
* Numeric multiplication: 5 * 6 is 30.
/ Numeric integer division: 30 / 5 is 6. Note that any
fractional part of the division is discarded, so 5 / 2 is 2.
+ Numeric addition: 3 + 4 is 7.
String concatenation: 'hello' + 'goodbye' yields 'hellogoodbye'.
List concatenation: [1 2] + [3 4] yields [1 2 3 4].
List extension: [1 2] + 3 yields [1 2 3].
- Numeric subtraction: 10 - 3 is 7.
List removal: [1 2 3] - 2 yields [1 3].
= Equality comparison; a = b yields true if the value of a
is the same as the value of b, nil otherwise. Note that
the types of a and b must be suitable for comparison; you
can compare two numbers, strings, lists, objects, or
truth values (true and nil); but comparing a number to a
string is meaningless, and results in a run-time error.
<> Inequality comparison; a <> b is true if a is not equal
to b, nil otherwise.
> Greater than comparison. You can compare the magnitude
of two numbers or two strings.
< Less than comparison.
>= Greater than or equal.
<= Less than or equal.
and Logical product: a and b is true if both a and b are true,
nil otherwise. Note that if a is nil, b is not evaluated,
because the expression's result is nil regardless of the value
of b.
or Logical sum: a or b is true if either a or b is true, or
if both are true. Note that if a is true, b is not evaluated.
? : Conditional operator: cond ? a : b yields the value of a
if cond is true, b if cond is nil. Note that only one of
a or b is evaluated.
:= Assignment.
+= Add and assign: the value of the variable or property on
the left has the value of the expression on the right added
to it. a += b is equivalent to a := a + b.
-= Subtract and assign.
*= Multiply and assign.
/= Divide and assign.
, Conjunction: the expression on the left of the comma is
evaluated, then the expression on the right is evaluated
and used as the value of the entire conjunction expression.
So, the value of (a, b) is b.
------------------------------------------------------------------------------
Self
Within a method, there's a special pseudo-object called "self" that
lets you determine the object whose method is being evaluated. This
may sound pretty useless, but consider a situation in which you're
defining an object that inherits from another object.
book: item
sdesc =
{
"The book is "; self.color; ". ";
}
;
redbook: book
color = "red"
;
bluebook: book
color = "blue"
;
Here, the superclass object, book, can define a generic sdesc method
which automatically adjusts to the individual subclass objects.
When redbook.sdesc is evaluated, the actual sdesc code that is
executed is inherited from book; however, since it is redbook.sdesc
that is being evaluated, self is set to redbook, so when book.sdesc
evaluated self.color, "red" is displayed. Likewise, when bluebook.sdesc
is evaluated, self is set to bluebook, so self.color displays "blue".
The classes defined in ADV.T make extensive use of this mechanism
to allow them to define generic attributes of the classes which
automatically customize themselves to the objects you create in
your game, even though the authors of ADV.T couldn't possibly
have known anything about your objects beforehand.
------------------------------------------------------------------------------
When do Methods Run?
Before we talk about how to write TADS methods in any more detail, we
should look at how the TADS parser works, because it's the parser that
decides what methods to call.
When the player types a command to TADS, the parser breaks the command
into a series of words. The parser then looks through its tables of
vocabulary words to see what objects are associated with those words;
remember that the special properties verb, noun, plural, adjective,
article, and preposition associate an object with a set of vocabulary
words. Once the parser has determined which objects are involved in
the command, it calls a series of methods in those objects; these
methods do all the work of processing the command. Since the methods
are defined in the objects, you can change almost any aspect of the
behavior of a TADS game; however, thanks to inheritance, you don't
have to change anything if you don't want to -- you can use the
definitions from ADV.T as they are, filling in only the necessary
descriptive text and properties.
The command is associated with a set of objects; each object is
classified according to its function in the command. The objects
are the actor, the verb, an optional set of direct objects, and
an optional preposition and indirect object. If the player didn't
direct the command to a different character (for example: "floyd,
give me the ball"), the player-actor, Me, is assumed.
The sequence of method calls is shown below. The actual objects
making up the command are substituted for the items in <angle
brackets>. If one of the objects is not used in the command,
the corresponding object parameter will be nil (for example,
if there is no direct object, <dobj> will be replaced by nil).
In the list below, you'll also see something named <verb-prefix>.
This is a special string that is defined in the deepverb objects;
we'll discuss this below. If the <verb-prefix> is, for example,
'Take', then the method meant by do<verb-prefix> is doTake.
<actor> . roomCheck( <verb> )
<actor> . actorAction( <verb>, <dobj>, <prep>, <iobj> )
<actor> . location . roomAction( <actor>, <verb>, <dobj>, <prep>, <iobj> )
If an indirect object was specified:
<dobj> . verDo<verb-prefix>( <actor>, <iobj> )
If no output resulted from verDo<verb-prefix>:
<iobj> . verIo<verb-prefix>( <actor> )
If no output resulted from verIo<verb-prefix>:
<iobj> . io<verb-prefix>( <actor>, <dobj> )
Else if a direct object was specified:
<dobj> . verDo<verb-prefix>( <actor> )
If no output resulted from verDo<verb-prefix>:
<dobj> . do<verb-prefix>( <actor> )
Else:
<verb> . action( <actor> )
Run each daemon that is currently active
Run and remove each fuse that has burned down to zero turns
The <verb-prefix> is read from the verb object when a direct or
indirect object is present. The <verb-prefix> is a string specified
by the property doAction when only a direct object is present, or
from the appropriate ioAction when both a direct and an indirect object
are present. For example, consider this verb object:
takeVerb: deepverb
verb = 'take'
sdesc = "take"
doAction = 'Take'
ioAction(outofPrep) = 'TakeOut'
ioAction(awayPrep) = 'TakeAway'
;
If the command is "take ball", only a direct object is present, so
the doAction property is used, and hence the <verb-prefix> is 'Take'.
This means that the methods called in the direct object will be
verDoTake and doTake. In this case, the sequence of methods that
the parser calls would be:
Me.roomCheck(takeVerb)
Me.actorAction(takeVerb, ball, nil, nil)
Me.location.roomAction(Me, takeVerb, ball, nil, nil)
ball.verDoTake(Me)
if no ouput resulted from verDoTake:
ball.doTake(Me)
If the command is "take ball out of box", the ioAction property
matching the preposition "out of" is used; this is 'TakeOut'. So,
the properties called in the objects are verDoTakeOut, verIoTakeOut,
and ioTakeOut. In this case, the sequence of methods called by
the parser would be:
Me.roomCheck(takeVerb)
Me.actorAction(takeVerb, ball, box, outofPrep)
Me.location.roomAction(Me, takeVerb, ball, box, outofPrep)
ball.verDoTakeOut(Me, box)
If no output resulted from verDoTakeOut:
box.verIoTakeOut(Me)
If no output resulted from verIoTakeOut:
box.ioTakeOut(Me, ball)
This sequence may look terribly complicated, but generally you won't
have to customize or even think about very much of it. In almost
every case, you'll be able to achieve the effect you want by customizing
the verDo<prefix>, do<prefix>, verIo<prefix>, and io<prefix> methods.
Note that the verDo<prefix> and verIo<prefix> methods are intended
to be used to verify that the object can be used in this command,
and the do<prefix> and io<prefix> methods are supposed to actually
carry out the command. The way this works is that the verDo<prefix>
and verIo<prefix> methods should display an error message if the
object cannot be used for this command, and should do nothing at
all if the object is usable. This may sound like a strange way
to decide whether the object is valid, but it makes it extremely
convenient to write these methods -- all you have to do is display
an error message if appropriate during verification.
------------------------------------------------------------------------------
Programming Statements
Within a method, or within a function, you can use a number of
statements that control execution.
local <variable-list> ;
This statement can be used only at the very start of a block
of code (that is, immediately following an open brace). The
"local" statement defines a list of variables that are private
to the block of code; these variables cannot be used outside
of the block. Variable names follow the same rules as other
identifiers, such as object and property names: they must
start with a letter, and consist of letters, numbers, and
underscores.
Example:
ldesc =
{
local cnt, loc;
cnt := 0;
loc := nil;
}
return <expression> ;
Return to the caller. The <expression> is evaluated, and the result
is supplied to the caller as the value of the method or function.
Control is returned to the caller at the point immediately following
the call to the current method or function; no further statements
in the current method or function are executed.
return ;
Return to the caller without providing a value.
if ( <expression> ) <statement1> else <statement2>
Evaluate the <expression>; if the value is not nil or 0, execute
<statement1>. Otherwise, execute <statement2>. Note that the
entire "else" clause is optional; if it is not provided, and the
value of <expression> is nil or 0, execution resumes following
<statement1>.
Note that <statement1> and <statement2> can either be a single
statement, or can be a series of statements enclosed in braces.
Example:
if (torch.islit)
{
"You suddenly realize that the odor was coal gas,
and that you're carrying a burning torch. You
try to retreat, but it's too late; the torch
ignites the gas, resulting in a terrible explosion.";
die();
}
else
"You realize that the odor was coal gas.
Fortunately, there's no open flame here.";
switch ( <expression> )
{
case <constant1>: <statements1>;
case <constant2>: <statements2>;
default: <statements3>;
}
This statement allows you to choose a number of execution
paths, based on the value of an expression. The <expression>
is evaluated, then compared to the various constants at the case
labels. If a case label matches the value of the expression,
execution resumes at the statement following that case label.
If no case label matches the value, and a default label is
present, execution resumes following the default. The default
label is optional.
Note that execution is not interrupted by hitting another
case label, but just continues into the statements following it.
Note also that a case label need not have any statements at all
following it. If you want to exit the switch statement, you
must explicitly code a break statement; this causes execution
to resume following the end of the switch statement.
Example:
switch(x)
{
case 1:
case 2:
"x is 1 or 2";
break;
case 3:
"x is 3";
/* note the lack of a break, so we fall through to next case */
case 4:
"x is 3 or 4";
break;
default:
"x is 5 or more";
}
while ( <expression> ) <statement>
Execute <statement> repeatedly as long as the value <expression>
is not nil or 0. The <expression> is evaluated before each
execution of <statement>, so if the value of <expression> is
nil or 0 before the first execution of the loop, the <statement>
is not executed at all. As with the if statement, the <statement>
can be either a single statement, or a block of statements enclosed
in braces.
Example:
while (cnt < length(lst))
{
"The next item is: "; say(lst[cnt]); "\n";
++cnt;
}
do <statement> while ( <expression> );
This statement is similar to the while statement, but evaluates
<expression> after each iteration of the loop. Thus, the <statement>
is always executed at least once, since the <expression> is not
tested for the first time until after the first iteration of the
loop.
Example:
do
{
"cnt = "; say(cnt); "\n";
--cnt;
} while (cnt > 0);
for ( <init-expr> ; <cond-expr> ; <reinit-expr> ) <statement>
This is a more general form of loop. First, the <init-expr> is
evaluated; this is done only once, before the loop starts iterating.
For each iteration of the loop, TADS first evaluates the <cond-expr>.
If it is nil or 0, the loop terminates; otherwise, <statement> is
executed. Finally, <reinit-expr> is evaluted. Note that any
of the expressions may be omitted; if the <cond-expr> is not
present, it is as though the expression were always true.
The for statement is often more convenient to code, but it
is always possible to write an equivalent loop using the
while statement.
Example:
for (cnt := 1 ; cnt < length(lst) ; ++cnt)
{
"the next element is: "; say(lst[cnt]); "\n";
}
break;
Exits from a while, do-while, or for loop, or from a switch
statement. Control immediately resumes following the end of
the loop or switch. The break statement is useful when the
condition for exiting a loop is most conveniently calculated
somewhere in the middle of the loop's processing.
continue;
Returns to the beginning of a while, do-while, or for loop.
The statements following the continue statement are skipped
for this iteration of the loop. In a for loop, the <reinit-expr>
is evaluated after a continue statement.
pass <property-name>;
When a method is executing, and the method has overridden a
method in a superclass object, the pass statement will cause
control to be passed to the superclass method that the current
method overrides. Note that execution never returns to the
current method after a pass statement. Note also that the
<property-name> must match the name of the current property.
exit;
Terminate all processing for the current player command,
and skip everything up to the daemons and fuses. This
statement is used when the current method has done everything
necessary to finish the command, and no further processing
is desirable.
abort;
Terminate all processing for the current player command,
skip the daemons and fuses, and go on to the next command.
This statement is normally used by "system" verbs, such as
"save" and "restore", to prevent any time from passing (time
in the game is handled by the fuses and daemons).
askdo;
Abort the current command, and ask the player to specify a
direct object.
askio( <prep-object> );
Abort the current command, supply <prep-object> as the preposition
for the command, and ask the player to specify an indirect object.
------------------------------------------------------------------------------
Built-in Functions
TADS has a number of built-in functions that you can call from
your game program. Some of these functions simply provide useful
utility functions, while others affect execution of the game.
A brief description of each function is shown below.
askfile(prompt) - asks the player for a filename; "prompt" is a
string (single-quoted) that specifies a prompt to display to the
player. Where appropriate, the standard system file selector is
used to ask the player for the file.
caps() - forces the next character displayed to be capitalized.
car(list) - returns the first element of a list. For
example: car([1 2 3]) returns 1.
cdr(list) - returns a list with its first element removed. For
example: cdr([1 2 3]) returns [2 3].
cvtnum(str) - converts a (single-quoted) string containing the
textual representation of a number to a numeric value. For
example, cvtnum('1234') returns 1234.
cvtstr(num) - converts a number to a string containing a textual
representation of the number. For example, cvtstr(100) returns '100'.
datatype(val) - returns the datatype of the "val" (after evaluating
the value). The return values are:
1 - number
2 - object
3 - string
5 - nil
7 - list
8 - true
10 - function pointer
13 - property pointer
defined(obj, &prop) - returns true if the object "obj" defines or
inherits property "prop", nil otherwise.
find(value, target) - returns the offset of "target" within "value".
If "value" is a list, this function returns the index of the "target"
within the list, or nil if it does not occur in the list. For
example, find([5 4 3], 4) returns 2. If "value" is a string,
this function returns the offset of the substring "target", or
nil if there is no such substring. For example, find('abcdef', 'cde')
returns 3.
firstobj() - begins a loop over all non-class objects in a game.
Returns an object. See nextobj(obj).
firstobj(cls) - begins a loop over all non-class objects with
superclass "cls". See nextobj(obj, cls).
getarg(num) - return the value of argument number "num" to the current
method or function.
incturn() - increments the turn counter, which moves all fuses
one turn closer to firing. Normally, this function is called
by a daemon function once per turn.
input() - allows the user to enter a line of text, and returns
the value as a string.
isclass(obj, cls) - returns true if "obj" inherits from superclass
"cls", nil otherwise.
length(val) - returns the number of characters in a string, or
the number of elements in a list.
logging(val) - if "val" is a string, creates the file named by
the string, and logs all text displayed on the screen to the file.
If "val" is nil, closes the current log file and stops logging.
lower(str) - returns a string consisting of all of the characters
of "str" converted to lower case.
nextobj(obj) - returns the object following "obj", in an arbitrary
order, or nil if the last object has been reached. Every non-class
object in the game will be returned exactly once by a loop such as
this:
local obj;
for (obj := firstobj() ; obj ; obj := nextobj(obj)) /* ... */;
nextobj(obj, cls) - returns the next object following "obj"
with superclass "cls", or nil if the last such object has been
reached. Every non-class object in the game with superclass cls
will be returned by a loop such as this:
local obj;
for (obj := firstobj(cls) ; obj ; obj := nextobj(obj, cls)) /* ... */;
notify(obj, &prop, turns) - establish a notifier. The property "prop"
of object "obj" (i.e., obj.prop) is called once after the number of turns
specified by "turns" has elapsed. If "turns" is zero, obj.prop is called
after every turn.
proptype(obj, &prop) - returns the type of property "prop" in object
"obj", without evaluating the property. If the property contains
method code, the code is not called by this function, so the return
type of the property cannot be determined; instead, proptype simply
returns 6, to indicate that the property contains method code. The
return values are:
1 - number
2 - object
3 - single-quoted string
5 - nil
6 - code
7 - list
8 - true
9 - double-quoted string
10 - function pointer
13 - property pointer
quit() - end the game.
rand(lim) - return a random number from 0 to "lim", inclusive.
randomize() - seed the random number generator with a value chosen
by the system. This function is called only once, at the beginning
of the game, to choose a random number seed. If this function is
not called, the sequence of numbers returned by rand() is the same
each time the game is played.
remdaemon(function, value) - remove a daemon previously set by
the setdaemon() built-in function. The daemon set with the same
values of "function" and "value" will be removed; if no such daemon
is found, a run-time error is reported.
remfuse(function, value) - remove a fuse previously set
by the setfuse() built-in function.
restart() - start the game over from the beginning.
restore(filename) - restore the game position stored in the file
named by the string "filename".
save(filename) - save the current game position to the file
named by the string "filename".
say(value) - display the value, which can be a single-quoted string
or a number.
setdaemon(function, value) - set a daemon. "function" will be
called once after every turn, with "value" as its argument. "value"
is an arbitrary value that is provided to allow you to pass
any information to the daemon that it may need. The daemon
function should be defined prior to its use in setdaemon().
setfuse(function, time, value) - set a fuse. "function" will be
called after the number of turns specified in "time" has elapsed.
"value" is an arbitrary value that is passed to the function as
its argument; you can specify any value that you find useful here.
setit(obj) - set "it" to the specified object. This changes the
object that the player refers to with the word "it".
setscore(score, turns) - set the score displayed on the status line.
"score" and "turns" are numbers.
setscore(str) - specify an arbitrary single-quoted string that will
be displayed on the status line in place of the normal score/turns
indicator.
substr(str, ofs, len) - returns the substring of "str" starting
at offset "ofs" and going for "len" characters. For example,
substr('abcdef', 3, 2) returns 'cd'.
undo() - undoes one turn.
unnotify(obj, &prop) - remove the notifier previously set on obj.prop.
If there is no such notifier, a run-time error is reported.
upper(str) - returns the string "str" with its characters converted
to upper-case.
yorn() - waits for the player to answer YES or NO. Returns
1 if the player typed YES, 0 if the player typed NO, and -1 if
the player typed anything else. Note that only the first
character of the player's response is checked, and the response
can be upper- or lower-case.
------------------------------------------------------------------------------
Where to go from Here
There's a lot more to TADS than we are able to describe here.
To learn more, you can start by looking at the example game
"Ditch Day Drifter" that we've included with the TADS shareware
distribution. While this game doesn't do everything that you can
do with TADS (in fact, since TADS is so flexible, it doesn't even
really scratch the surface), it does contain examples of many
common adventure game situations that may help you in designing
your own game.
For full details on TADS, you should obtain the TADS Author's
Manual. When you register your copy of TADS, we'll send you the
complete printed TADS Author's Manual, which contains over 200
pages of detailed information on the language and the system.
Some of the topics covered in the TADS Author's Manual:
- An overview of the concepts used in the TADS programming
language.
- A detailed description of the TADS programming language,
including how to define functions and objects, how to write
procedural code, and how to write expressions.
- How to use the TADS Debugger, a powerful source-level
debugger that you get when you register your copy of TADS.
- Descriptions of all of the built-in functions.
- Descriptions of the objects in ADV.T, and how to use them.
- An introduction to object-oriented programming.
- How the player command parser works: how it reads commands,
disambiguates nouns, and calls your program to carry out
commands.
- Descriptions of the special properties that the system
calls in your game objects.
- Descriptions of the special objects that your game
program must provide.
- Explanations of system error messages.
- Details on how to write and use external functions written
in a language such as C.
- How to use the compiler and run-time system.
- Advice on the what to do (and what not to do) when designing
a game to make your game more fun to play.
In addition, the TADS Author's Manual contains a large number
of detailed examples that show you how to write your own games.
The chapter "Getting Started with TADS" shows you how to design
a game, and how to turn your design into a TADS program. The
chapter "Advanced TADS Techniques" provides 40 pages of examples
with full explanations, showing how to implement a wide variety
of game situations, such as:
- Creating your own verbs
- Using doors and other obstacles
- Creating vehicles
- Hiding objects
- Creating non-player characters
If that's not enough to convince you to register, remember that
only registered users get the TADS Debugger, a full-featured source-
level debugger that can make it much easier to get your games
working. You'll also receive the latest version of the TADS
software on disk. In addition, you'll be helping to ensure that
we can continue to develop and improve TADS in the future.
------------------------------------------------------------------------------
Contacting High Energy Software
If you have any questions or comments about TADS, please feel free
to contact us. We're interested in hearing your ideas for improving
TADS, and for how we can improve this overview user's guide. We'd
also be happy to try to answer any questions you have about our
system's capabilities.
High Energy Software BBS: The best way to reach us is our on-line
support bulletin board system. You can call our BBS at (415)493-2420
any time (set your modem to 9600-N-8-1), and send mail to the Sysop.
Feel free to browse through the conferences on the system -- you may
be able to find the answer to your question without having to wait
for a reply.
Electronic Mail: We can be reached by CompuServe, GEnie, and Internet
users. Send mail to the appropriate address listed below. Electronic
mail is usually the fastest way of reaching us.
CompuServe: 73737,417
GENie: M.ROBERTS10
Internet: 73737.417@compuserve.com
Postal Mail: Our US Mail address is:
High Energy Software
PO Box 50422
Palo Alto, CA 94303